﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace JH
{
    /// <summary>
    ///  C#对象扩展
    /// </summary>
    public static class ObjectExtension
    {
        /// <summary>
        ///  缓存属性信息，避免每次都反射获取
        /// </summary>
        private static readonly Dictionary<Type, PropertyInfo[]> PropertyInfoCache =
            new Dictionary<Type, PropertyInfo[]>();

        /// <summary>
        ///  从一个类型向另一个类型赋值时的属性缓存
        /// </summary>
        private static readonly Dictionary<(Type, Type), List<PropertyInfo>> TypePairCache =
            new Dictionary<(Type, Type), List<PropertyInfo>>();

        /// <summary>
        /// 从一个对象中复制属性值，到另一个对象
        /// </summary>
        /// <param name="target">赋值给哪个实例</param>
        /// <param name="origin">从哪个实例获取值</param>
        /// <param name="throwIfError">false时，赋值异常不会打断赋值,默认会抛出异常</param>
        /// <exception cref="ArgumentNullException">任意参数为空，会抛出异常</exception>
        /// <exception cref="AggregateException">属性赋值异常</exception>
        public static void CopyPropertyValueFromEm(this object target, object origin, bool throwIfError = true)
        {
            if (target == null || origin == null)
                throw new ArgumentNullException(target == null
                    ? nameof(target)
                    : nameof(origin), "源对象或目标对象为空");

            // 获取源对象到目标对象赋值的属性集合
            var commonProperties = GetTypePair(origin, target);

            //遍历相同的属性
            var exceptionList = new List<Exception>();
            foreach (var property in commonProperties)
            {
                try
                {
                    //获取目标值
                    var value = property.GetValue(origin);

                    //设置值
                    property.SetValue(target, value);
                }
                catch (Exception e)
                {
                    exceptionList.Add(e);
                }
            }

            if (throwIfError && exceptionList.Count > 0)
            {
                throw new AggregateException("复制属性值时发生异常", exceptionList);
            }
        }

        /// <summary>
        ///  获取一组从一个类型向另一个类型赋值时的属性缓存
        /// </summary>
        private static List<PropertyInfo> GetTypePair(object origin, object target)
        {
            //原始类型
            var originType = origin.GetType();

            //目标类型
            var targetType = target.GetType();

            //如果已经缓存过,直接返回
            if (TypePairCache.TryGetValue((originType, targetType), out var commonProperties))
                return commonProperties;

            //源属性集合
            if (!PropertyInfoCache.TryGetValue(originType, out var originProperties))
            {
                PropertyInfoCache[originType] = originProperties =
                    originType.GetProperties(originType.GetCommonlyUseFlagsEm());
            }

            //目标属性集合
            if (!PropertyInfoCache.TryGetValue(targetType, out var targetProperties))
            {
                PropertyInfoCache[targetType] = targetProperties =
                    targetType.GetProperties(targetType.GetCommonlyUseFlagsEm());
            }

            //筛选出相同标识符，相同类型的属性
            commonProperties = new List<PropertyInfo>();
            foreach (var property in targetProperties)
            {
                var originProperty = originProperties.FirstOrDefault(tp =>
                    tp.Name == property.Name && tp.PropertyType == property.PropertyType);

                // 如果源对象中没有目标属性
                if (originProperty == null) continue;

                //如果源对象不可读或者目标对象不可写
                if (!originProperty.CanRead || !property.CanWrite) continue;

                //加入筛选
                commonProperties.Add(property);
            }

            //加入缓存
            TypePairCache[(originType, targetType)] = commonProperties;

            //返回
            return commonProperties;
        }
    }
}